Introduction to Leaflet
in R
Basic Usage
Basemaps
Add a basemap with addTiles(). By default OpenStreetMap
tiles are used.
Base maps are not required. Without a basemap your map will be
floating in space. Using a basemap adds context, especially for point
data
leaflet() %>%
setView(lng = -77.5, lat = 37.5, zoom = 10) %>% #not necessary to set your view
addTiles()
This is a blank map because we haven’t added any layers
Third party tiles
Use addProviderTiles() function to use a different
basemap. Think about what information you are trying to convey when
choosing a basemap. Will extra detail be helpful or will it distract
from your data? Most third party base map styles have a labeled and an
unlabeled option.
Base maps can be combined if, for example, you like the style of one
basemap but the labels of another basemap. You can also include multiple
basemap layers and let the viewer select their prefered map. We’ll see
how to do this later.
See
here for all options
leaflet() %>%
setView(lng = -77.5, lat = 37.5, zoom = 10) %>% #not necessary to set your view
addProviderTiles(providers$CartoDB.Positron)
Data Objects
Both leaflet() and each map layer have a
data = parameter. Spatial data can be in the form of:
- dataframe with lat/lng columns
sp objects
sf objects
maps package map() objects
Leaflet only uses WGS84 for displaying data. Leaflet
can project coordinates automatically, but projecting your data to WGS84
(crs = 4326) is a good habit to get into.
Data can be passed through the leaflet() function or
through the map layers.
Here we define our data in the leaflet() function
leaflet(beetle) %>%
addTiles() %>%
addCircles()
Here we define the data in the map layer
(addCircles())
leaflet() %>%
addTiles() %>%
addCircles(data = beetle)
Markers
Points can be plotted using addMarkers() or
addCircles()
Default markers
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addMarkers()
zoom in/out and notice that the markers stay the same size
Makers can be customize using
options = markerOptions()
Custom Markers
bug <- icons(
iconUrl = "https://www.pngfind.com/pngs/m/14-144860_beetle-bug-png-transparent-image-bug-png-png.png",
iconWidth = 20,
iconHeight = 20
)
Use your custom marker in the icons = of the
addMarkers() function
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addMarkers(icon = bug)
Marker from a library
link
to Font Awesome library
makeAwesomeIcon() allows you to select your icon and
edit its color ect.
addAwesomeMarkers() allows you to add your custom icon
to the map
fa_bug <- makeAwesomeIcon(icon = "bug", library = "fa",
markerColor = "cadetblue", iconColor = "beige")
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addAwesomeMarkers(icon = fa_bug)
Notice that our custom icon using makeAwesomeIcon() and
addAwsomeMarkers() displays the icon on top of a marker.
The custom icon using addMarkers() displays directly on the
map
Pop-ups and Labels
Pop-ups
Pop-ups can be added as a stand-alone feature using
addPopups(), or add to appear when a shape is clicked
Stand-alone
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
setView(lng = -111, lat = 25, zoom = 7) %>%
addPopups(lng = -111, lat = 25, paste0("beetles live here"))
Notice that the pop-up is its own leaflet layer
As a marker option
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addAwesomeMarkers(icon = fa_bug, popup = paste0("Site:", beetle$Site))
Notice that this time we define our pop-up inside the maker layer,
not as its own layer
Labels
Use label = to add a label displayed on a mouse over
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addAwesomeMarkers(icon = fa_bug, label = paste0("Site:", beetle$Site))
Labels and Pop-ups can be customized using the
labelOptions = labelOptions() function. You can define the
text size, color, font, box border, box shadow, ect.
Adding Circles, Lines and Polygons
addCircles()
addLines()
addPolygons()
Circles, lines and polygons behave very similarly in leaflet. For
this lecture we will continue using our beetle point data. There is an
example using polygons at the end of the lecture.
addCircles()
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles()
If you zoom in far enough you will see that the size of the circles
changes with the zoom level. This feature will become more apparent in
later examples.
Change the size (radius)
In this example the size of the circles is proportional to the number
of males at each site. The value is multiplied by 500 so the different
sizes can be easily visualized when zoomed out.
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles(radius = ~Males *500, stroke = FALSE, fillOpacity = .5)
Color
color will define the stroke and the fill color unless
fillColor is specified
In this example the color of the circles is now proportional to the
male to female ratio (beetle$MFRatio) of each site. Our
palette is defined before hand using colorNumeric() and
stored as pal.
pal <- colorNumeric(
palette = "RdBu", # Red to Blue palette
domain = beetle$MFRatio
)
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles(color = ~pal(MFRatio), fillOpacity = .7, radius = 15000, stroke = FALSE,
label = paste0(beetle$MFRatio))
Darker blue circles represent sites with a larger male to female
ratio. Darker red circles represent sites with a smaller male to female
ratio. We will see how to add a legend later in the lecture.
Highlight
Individual shapes can be highlighted when hovered over using
highlightOptions within the shape layer.
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles(fillColor = pal(beetle$MFRatio), fillOpacity = .7, radius = 15000,
weight = 1, color = "black",
highlightOptions = highlightOptions(color = "yellow", weight = 4,
bringToFront = TRUE))
Hover your mouse over different circles and notice the the stroke
color changes from black to yellow and becomes thicker. If circles
overlap, hovering now brings that circle in front of the others.
Map Groups
Assign layers to groups using group =. Assigning groups
allows you to show/hide layers or control the visibility of layers
through the function addLayersControl()
A group can be made up a a single layer or multiple layers, but each
layer can only belong to one group.
Layer Controls
Once your data layers have been assigned to groups you can use
addLayersControl() to control individual layers
In this example sites with more males than females are blue and sites
with more females than males are pink. Because we want to control the
two different types of circles separately, they need to be added as
individual layers. The group = is defined inside each
addCircles() layer.
Under the addLayersControl() layer we can select which
groups to control. In this case I want to be able to turn the layers
on/off. overlayGroups can be individually checked or
unchecked.
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles(data = (beetle %>% filter(Males > Females)),
fillColor = "lightblue", fillOpacity = 1,
color = "blue", radius = 10000, weight = 1,
group = "males") %>%
addCircles(data = (beetle %>% filter(Males < Females)),
fillColor = "pink", fillOpacity = 1,
color = "red", radius = 10000, weight = 1,
group = "females") %>%
addLayersControl(overlayGroups = c("males", "females"),
options = layersControlOptions(collapsed = FALSE))
Base Groups
baseGroups can only be viewed one group at a time and
one group is always selected (can’t turn them all off)
Base groups are useful when providing viewers a choice of basemaps.
In the example below, three different tile layers are added, each their
own group. Now in addLayersControl() we can include these
basemap groups in the baseGroups.
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron, group = "Default") %>%
addProviderTiles(providers$OpenStreetMap, group = "Open Street Maps") %>%
addProviderTiles(providers$Stamen.Toner, group = "Stamen Toner") %>%
addCircles(data = (beetle %>% filter(Males > Females)),
fillColor = "lightblue", fillOpacity = 1,
color = "blue", radius = 10000, weight = 1,
group = "males") %>%
addCircles(data = (beetle %>% filter(Males < Females)),
fillColor = "pink", fillOpacity = 1,
color = "red", radius = 10000, weight = 1,
group = "females") %>%
addLayersControl(overlayGroups = c("males", "females"),
options = layersControlOptions(collapsed = FALSE),
baseGroups = c("Default", "Open Street Maps", "Stamen Toner"))
Notice the difference between overlay groups (circles) and base
groups (basemaps)
Zoom Levels
Want to display more detail when the map in zoomed in and less detail
when the map is zoomed out? This is done with zoomLevels in
the groupOptions()
beetle
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles(fillColor = "lightblue", fillOpacity = 1,
color = "blue", radius = 10000, weight = 1,
group = "beetles") %>%
groupOptions("beetles", zoomLevels = 5:8)
Zoom in and out to watch the layer turn on/off
Clustering
When there are a larger number of makers on a map you can cluster
them using clusterOpions =
A common example of clustering is the Dominion
Power outage map.
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(fillColor = "lightblue", fillOpacity = 1,
color = "blue", weight = 1,
group = "beetles",
clusterOptions = markerClusterOptions())
Hovering over a cluster marker with your mouse allows you to see the
coverage of the cluster
Click on a cluster to zoom in to the cluster bounds
All of these features can be changed
markerClusterOptions. markerClusterOptions
also allows you to freeze the clustering at a defined zoom level with
freezeAtZoom =
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(fillColor = "lightblue", fillOpacity = 1,
color = "blue", weight = 1,
group = "beetles",
clusterOptions = markerClusterOptions(
showCoverageOnHover = FALSE,
freezeAtZoom = 6))
Now the clusters stay the same regardless of zoom level. When the
cluster is clicked you can see each point included in the cluster
Legends
Legends are added using addLegend()
The example below shows a legend with categorical data
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles(data = (beetle %>% filter(Males > Females)),
fillColor = "lightblue", fillOpacity = 1,
color = "blue", radius = 10000, weight = 1,
group = "males") %>%
addCircles(data = (beetle %>% filter(Males < Females)),
fillColor = "pink", fillOpacity = 1,
color = "red", radius = 10000, weight = 1,
group = "females") %>%
addLegend( colors = c("lightblue", "pink"),
labels = c("More Males", "More Females"),
opacity = 1)
The next example shows a legend with continuous data. Notice that
pal is used this time instead of color and
values instead of labels
pal <- colorNumeric(
palette = "RdBu",
domain = beetle$MFRatio)
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircles(fillColor = ~pal(MFRatio), fillOpacity = .7, radius = 15000,
weight = 1, color = "grey") %>%
addLegend(pal = pal, values = ~MFRatio,
title = "Male to Female Ratio")
Scale Bars
Scale bars are added using addScaleBar(). The scale bar
will adjust itself as you zoom in and out
leaflet(beetle) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(fillColor = "lightblue", fillOpacity = 1,
color = "blue", weight = 1) %>%
addScaleBar(position = "bottomright")
You can choose to use metric and/or imperial units and adjust the
scale bar width using options = scaleBarOptions.
Making a Choropleth Map
Lines and Polygons work in a very similar manner to circles
For this example we’ll use census tracts from the tigris
package
library(tidyverse)
library(tigris)
options(tigris_use_cache = TRUE)
tracts <- tracts("VA", "Richmond city") %>%
st_transform(4326)
Defining the color palette using the land area of each tract. Area is
given in square meters. To convert to square miles we divide our area by
2.59e+6
pal <- colorNumeric(palette = "Blues",
domain = tracts$ALAND/2.59e+6)
The polygon colors are assigned using
fillColor = ~pal(ALAND/2.59e+6)
Labels, legend and scale bar are added to give the map a more
polished finish
leaflet(tracts) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(fillColor = ~pal(ALAND/2.59e+6), fillOpacity = 1, smoothFactor = .2,
color = "darkgrey", weight = 1,
highlightOptions = highlightOptions(color = "white", weight = 2, opacity = 1,
bringToFront = TRUE),
label = paste(round(tracts$ALAND/2.59e+6, digits = 2), "sq. mi.")
) %>%
addLegend(pal = pal, values = ~ALAND/2.59e+6, title = "Area (sq. miles)") %>%
addScaleBar(position = "bottomright")
The map is now shaded so that the larger census tracts are darker
blue and the smallest tracts are lighter blue
LS0tCnRpdGxlOiAiTGVhZmxldCIKYXV0aG9yOiAiQ2hhcmlzIERlYWR3eWxlciIKZGF0ZTogIjMvMTUvMjAyMSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShzZikKbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKW0ludHJvZHVjdGlvbiB0byBMZWFmbGV0IGluIFJdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8pCgojIyBCYXNpYyBVc2FnZQoKIyMjIE1hcCBXaWRnZXQKCioqY3JlYXRlIG1hcCB3aWRnZXQgd2l0aCBgbGVhZmxldCgpYCoqCgoKbG9hZCBpbiB0aGUgZGF0YS4gV2UnbGwgdXNlIHRoZSBiZWV0bGUgZGF0YSBhZ2Fpbi4KYGBge3J9CmJlZXRsZV91cmwgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9keWVybGFiL0VOVlMtTGVjdHVyZXMvbWFzdGVyL2RhdGEvQXJhcHR1c19EaXNwZXJhbF9CaWFzLmNzdiIKYmVldGxlIDwtIHJlYWQuY3N2KGJlZXRsZV91cmwpCgpiZWV0bGUgPC0gYmVldGxlICU+JQogICBzdF9hc19zZiggY29vcmRzPWMoIkxvbmdpdHVkZSIsIkxhdGl0dWRlIiksIGNycz00MzI2ICkKYGBgCgojIyMgQmFzZW1hcHMKCkFkZCBhIGJhc2VtYXAgd2l0aCBgYWRkVGlsZXMoKWAuIEJ5IGRlZmF1bHQgT3BlblN0cmVldE1hcCB0aWxlcyBhcmUgdXNlZC4gCgpCYXNlIG1hcHMgYXJlIG5vdCByZXF1aXJlZC4gV2l0aG91dCBhIGJhc2VtYXAgeW91ciBtYXAgd2lsbCBiZSBmbG9hdGluZyBpbiBzcGFjZS4gVXNpbmcgYSBiYXNlbWFwIGFkZHMgY29udGV4dCwgZXNwZWNpYWxseSBmb3IgcG9pbnQgZGF0YQoKYGBge3J9CmxlYWZsZXQoKSAlPiUKICBzZXRWaWV3KGxuZyA9IC03Ny41LCBsYXQgPSAzNy41LCB6b29tID0gMTApICU+JSAjbm90IG5lY2Vzc2FyeSB0byBzZXQgeW91ciB2aWV3CiAgYWRkVGlsZXMoKQpgYGAKVGhpcyBpcyBhIGJsYW5rIG1hcCBiZWNhdXNlIHdlIGhhdmVuJ3QgYWRkZWQgYW55IGxheWVycwoKCiMjIyBUaGlyZCBwYXJ0eSB0aWxlcwoKVXNlIGBhZGRQcm92aWRlclRpbGVzKClgIGZ1bmN0aW9uIHRvIHVzZSBhIGRpZmZlcmVudCBiYXNlbWFwLiAgVGhpbmsgYWJvdXQgd2hhdCBpbmZvcm1hdGlvbiB5b3UgYXJlIHRyeWluZyB0byBjb252ZXkgd2hlbiBjaG9vc2luZyBhIGJhc2VtYXAuIFdpbGwgZXh0cmEgZGV0YWlsIGJlIGhlbHBmdWwgb3Igd2lsbCBpdCBkaXN0cmFjdCBmcm9tIHlvdXIgZGF0YT8gTW9zdCB0aGlyZCBwYXJ0eSBiYXNlIG1hcCBzdHlsZXMgaGF2ZSBhIGxhYmVsZWQgYW5kIGFuIHVubGFiZWxlZCBvcHRpb24uIAoKQmFzZSBtYXBzIGNhbiBiZSBjb21iaW5lZCBpZiwgZm9yIGV4YW1wbGUsIHlvdSBsaWtlIHRoZSBzdHlsZSBvZiBvbmUgYmFzZW1hcCBidXQgdGhlIGxhYmVscyBvZiBhbm90aGVyIGJhc2VtYXAuIFlvdSBjYW4gYWxzbyBpbmNsdWRlIG11bHRpcGxlIGJhc2VtYXAgbGF5ZXJzIGFuZCBsZXQgdGhlIHZpZXdlciBzZWxlY3QgdGhlaXIgcHJlZmVyZWQgbWFwLiBXZSdsbCBzZWUgaG93IHRvIGRvIHRoaXMgbGF0ZXIuCgpbU2VlIGhlcmVdKGh0dHA6Ly9sZWFmbGV0LWV4dHJhcy5naXRodWIuaW8vbGVhZmxldC1wcm92aWRlcnMvcHJldmlldy9pbmRleC5odG1sKSBmb3IgYWxsIG9wdGlvbnMKCmBgYHtyfQpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsbmcgPSAtNzcuNSwgbGF0ID0gMzcuNSwgem9vbSA9IDEwKSAlPiUgI25vdCBuZWNlc3NhcnkgdG8gc2V0IHlvdXIgdmlldwogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pCmBgYAogICAKCiMjIyBEYXRhIE9iamVjdHMKCkJvdGggYGxlYWZsZXQoKWAgYW5kIGVhY2ggbWFwIGxheWVyIGhhdmUgYSBgZGF0YSA9YCBwYXJhbWV0ZXIuIFNwYXRpYWwgZGF0YSBjYW4gYmUgaW4gdGhlIGZvcm0gb2Y6CgotIGRhdGFmcmFtZSB3aXRoIGxhdC9sbmcgY29sdW1ucwotIGBzcGAgb2JqZWN0cwotIGBzZmAgb2JqZWN0cwotIGBtYXBzYCBwYWNrYWdlIGBtYXAoKWAgb2JqZWN0cwoKTGVhZmxldCBvbmx5IHVzZXMgKipXR1M4NCoqIGZvciBkaXNwbGF5aW5nIGRhdGEuIExlYWZsZXQgY2FuIHByb2plY3QgY29vcmRpbmF0ZXMgYXV0b21hdGljYWxseSwgYnV0IHByb2plY3RpbmcgeW91ciBkYXRhIHRvIFdHUzg0IChjcnMgPSA0MzI2KSBpcyBhIGdvb2QgaGFiaXQgdG8gZ2V0IGludG8uCgoKRGF0YSBjYW4gYmUgcGFzc2VkIHRocm91Z2ggdGhlIGBsZWFmbGV0KClgIGZ1bmN0aW9uIG9yIHRocm91Z2ggdGhlIG1hcCBsYXllcnMuCgpIZXJlIHdlIGRlZmluZSBvdXIgZGF0YSBpbiB0aGUgYGxlYWZsZXQoKWAgZnVuY3Rpb24KYGBge3J9CmxlYWZsZXQoYmVldGxlKSAlPiUKICBhZGRUaWxlcygpICU+JQogIGFkZENpcmNsZXMoKQpgYGAKICAgCgpIZXJlIHdlIGRlZmluZSB0aGUgZGF0YSBpbiB0aGUgbWFwIGxheWVyIChgYWRkQ2lyY2xlcygpYCkKYGBge3J9CmxlYWZsZXQoKSAlPiUKICBhZGRUaWxlcygpICU+JQogIGFkZENpcmNsZXMoZGF0YSA9IGJlZXRsZSkKYGBgCgoKIyMgTWFya2VycwoKUG9pbnRzIGNhbiBiZSBwbG90dGVkIHVzaW5nIGBhZGRNYXJrZXJzKClgIG9yIGBhZGRDaXJjbGVzKClgCgotIE1hcmtlcnMgc3RheSB0aGUgc2FtZSBzaXplIHJlZ2FyZGxlc3Mgb2Ygem9vbSBsZXZlbAoKLSBDaXJjbGVzIHNjYWxlIHdpdGggdGhlIG1hcAoKCioqRGVmYXVsdCBtYXJrZXJzKioKYGBge3J9CmxlYWZsZXQoYmVldGxlKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRNYXJrZXJzKCkKYGBgCnpvb20gaW4vb3V0IGFuZCBub3RpY2UgdGhhdCB0aGUgbWFya2VycyBzdGF5IHRoZSBzYW1lIHNpemUKCk1ha2VycyBjYW4gYmUgY3VzdG9taXplIHVzaW5nIGBvcHRpb25zID0gbWFya2VyT3B0aW9ucygpYAoKCgoKKipDdXN0b20gTWFya2VycyoqCgotIEN1c3RvbSBtYXJrZXJzIGNhbiBiZSBtYWRlIHVzaW5nIGEgdXJsL2ltYWdlIGZpbGUgb3IgZnJvbSBsaWJyYXJpZXMgCgotIEl0IGlzIGltcG9ydGFudCB0byBkZWZpbmUgdGhlIGhlaWdodCBhbmQgd2lkdGggKGluIHBpeGVscykgb2YgeW91ciBpY29ucwoKYGBge3J9CmJ1ZyA8LSBpY29ucygKICBpY29uVXJsID0gImh0dHBzOi8vd3d3LnBuZ2ZpbmQuY29tL3BuZ3MvbS8xNC0xNDQ4NjBfYmVldGxlLWJ1Zy1wbmctdHJhbnNwYXJlbnQtaW1hZ2UtYnVnLXBuZy1wbmcucG5nIiwKICBpY29uV2lkdGggPSAyMCwKICBpY29uSGVpZ2h0ID0gMjAKKQpgYGAKClVzZSB5b3VyIGN1c3RvbSBtYXJrZXIgaW4gdGhlIGBpY29ucyA9YCBvZiB0aGUgYGFkZE1hcmtlcnMoKWAgZnVuY3Rpb24KYGBge3J9CmxlYWZsZXQoYmVldGxlKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRNYXJrZXJzKGljb24gPSBidWcpCmBgYAoKKipNYXJrZXIgZnJvbSBhIGxpYnJhcnkqKgoKW2xpbmsgdG8gRm9udCBBd2Vzb21lIGxpYnJhcnldKGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tL2ljb25zP2Q9Z2FsbGVyeSZwPTImbT1mcmVlKQoKYG1ha2VBd2Vzb21lSWNvbigpYCBhbGxvd3MgeW91IHRvIHNlbGVjdCB5b3VyIGljb24gYW5kIGVkaXQgaXRzIGNvbG9yIGVjdC4KCmBhZGRBd2Vzb21lTWFya2VycygpYCBhbGxvd3MgeW91IHRvIGFkZCB5b3VyIGN1c3RvbSBpY29uIHRvIHRoZSBtYXAKYGBge3J9CmZhX2J1ZyA8LSBtYWtlQXdlc29tZUljb24oaWNvbiA9ICJidWciLCBsaWJyYXJ5ID0gImZhIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWFya2VyQ29sb3IgPSAiY2FkZXRibHVlIiwgaWNvbkNvbG9yID0gImJlaWdlIikKCmxlYWZsZXQoYmVldGxlKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRBd2Vzb21lTWFya2VycyhpY29uID0gZmFfYnVnKQoKYGBgCiAgIApOb3RpY2UgdGhhdCBvdXIgY3VzdG9tIGljb24gdXNpbmcgYG1ha2VBd2Vzb21lSWNvbigpYCBhbmQgYGFkZEF3c29tZU1hcmtlcnMoKWAgZGlzcGxheXMgdGhlIGljb24gb24gdG9wIG9mIGEgbWFya2VyLiBUaGUgY3VzdG9tIGljb24gdXNpbmcgYGFkZE1hcmtlcnMoKWAgZGlzcGxheXMgZGlyZWN0bHkgb24gdGhlIG1hcAoKCiMjIFBvcC11cHMgYW5kIExhYmVscwoKLSBQb3AtdXBzIGFwcGVhci9kaXNhcHBlYXIgd2hlbiBjbGlja2VkCgotIExhYmVscyBhcHBlYXIvZGlzYXBwZWFyIHdoZW4geW91IG1vdXNlIGhvdmVycyBvbiB0aGUgb2JqZWN0CgoKIyMjIFBvcC11cHMKClBvcC11cHMgY2FuIGJlIGFkZGVkIGFzIGEgc3RhbmQtYWxvbmUgZmVhdHVyZSB1c2luZyBgYWRkUG9wdXBzKClgLCBvciBhZGQgdG8gYXBwZWFyIHdoZW4gYSBzaGFwZSBpcyBjbGlja2VkCgoKKipTdGFuZC1hbG9uZSoqCmBgYHtyfQpsZWFmbGV0KGJlZXRsZSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgc2V0VmlldyhsbmcgPSAtMTExLCBsYXQgPSAyNSwgem9vbSA9IDcpICU+JQogIGFkZFBvcHVwcyhsbmcgPSAtMTExLCBsYXQgPSAyNSwgcGFzdGUwKCJiZWV0bGVzIGxpdmUgaGVyZSIpKQpgYGAKTm90aWNlIHRoYXQgdGhlIHBvcC11cCBpcyBpdHMgb3duIGxlYWZsZXQgbGF5ZXIKIAogCgoqKkFzIGEgbWFya2VyIG9wdGlvbioqCmBgYHtyfQpsZWFmbGV0KGJlZXRsZSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkQXdlc29tZU1hcmtlcnMoaWNvbiA9IGZhX2J1ZywgcG9wdXAgPSBwYXN0ZTAoIlNpdGU6IiwgYmVldGxlJFNpdGUpKQpgYGAKTm90aWNlIHRoYXQgdGhpcyB0aW1lIHdlIGRlZmluZSBvdXIgcG9wLXVwIGluc2lkZSB0aGUgbWFrZXIgbGF5ZXIsIG5vdCBhcyBpdHMgb3duIGxheWVyCgojIyMgTGFiZWxzCgpVc2UgYGxhYmVsID0gYCB0byBhZGQgYSBsYWJlbCBkaXNwbGF5ZWQgb24gYSBtb3VzZSBvdmVyCmBgYHtyfQpsZWFmbGV0KGJlZXRsZSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkQXdlc29tZU1hcmtlcnMoaWNvbiA9IGZhX2J1ZywgbGFiZWwgPSBwYXN0ZTAoIlNpdGU6IiwgYmVldGxlJFNpdGUpKQpgYGAKCgpMYWJlbHMgYW5kIFBvcC11cHMgY2FuIGJlIGN1c3RvbWl6ZWQgdXNpbmcgdGhlIGBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMoKWAgZnVuY3Rpb24uIFlvdSBjYW4gZGVmaW5lIHRoZSB0ZXh0IHNpemUsIGNvbG9yLCBmb250LCBib3ggYm9yZGVyLCBib3ggc2hhZG93LCBlY3QuCgojIyBBZGRpbmcgQ2lyY2xlcywgTGluZXMgYW5kIFBvbHlnb25zCgotIGBhZGRDaXJjbGVzKClgCgotIGBhZGRMaW5lcygpYAoKLSBgYWRkUG9seWdvbnMoKWAKCkNpcmNsZXMsIGxpbmVzIGFuZCBwb2x5Z29ucyBiZWhhdmUgdmVyeSBzaW1pbGFybHkgaW4gbGVhZmxldC4gRm9yIHRoaXMgbGVjdHVyZSB3ZSB3aWxsIGNvbnRpbnVlIHVzaW5nIG91ciBiZWV0bGUgcG9pbnQgZGF0YS4gVGhlcmUgaXMgYW4gZXhhbXBsZSB1c2luZyBwb2x5Z29ucyBhdCB0aGUgZW5kIG9mIHRoZSBsZWN0dXJlLgoKCiMjIyBgYWRkQ2lyY2xlcygpYApgYGB7cn0KbGVhZmxldChiZWV0bGUpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZENpcmNsZXMoKQpgYGAKSWYgeW91IHpvb20gaW4gZmFyIGVub3VnaCB5b3Ugd2lsbCBzZWUgdGhhdCB0aGUgc2l6ZSBvZiB0aGUgY2lyY2xlcyBjaGFuZ2VzIHdpdGggdGhlIHpvb20gbGV2ZWwuIFRoaXMgZmVhdHVyZSB3aWxsIGJlY29tZSBtb3JlIGFwcGFyZW50IGluIGxhdGVyIGV4YW1wbGVzLgoKIyMjIENoYW5nZSB0aGUgc2l6ZSAoYHJhZGl1c2ApCgpJbiB0aGlzIGV4YW1wbGUgdGhlIHNpemUgb2YgdGhlIGNpcmNsZXMgaXMgcHJvcG9ydGlvbmFsIHRvIHRoZSBudW1iZXIgb2YgbWFsZXMgYXQgZWFjaCBzaXRlLiBUaGUgdmFsdWUgaXMgbXVsdGlwbGllZCBieSA1MDAgc28gdGhlIGRpZmZlcmVudCBzaXplcyBjYW4gYmUgZWFzaWx5IHZpc3VhbGl6ZWQgd2hlbiB6b29tZWQgb3V0LgpgYGB7cn0KbGVhZmxldChiZWV0bGUpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZENpcmNsZXMocmFkaXVzID0gfk1hbGVzICo1MDAsIHN0cm9rZSA9IEZBTFNFLCBmaWxsT3BhY2l0eSA9IC41KQpgYGAKCgojIyMgQ29sb3IKCmBjb2xvcmAgd2lsbCBkZWZpbmUgdGhlIHN0cm9rZSBhbmQgdGhlIGZpbGwgY29sb3IgdW5sZXNzIGBmaWxsQ29sb3JgIGlzIHNwZWNpZmllZAoKSW4gdGhpcyBleGFtcGxlIHRoZSBjb2xvciBvZiB0aGUgY2lyY2xlcyBpcyBub3cgcHJvcG9ydGlvbmFsIHRvIHRoZSBtYWxlIHRvIGZlbWFsZSByYXRpbyAoYGJlZXRsZSRNRlJhdGlvYCkgb2YgZWFjaCBzaXRlLiBPdXIgcGFsZXR0ZSBpcyBkZWZpbmVkIGJlZm9yZSBoYW5kIHVzaW5nIGBjb2xvck51bWVyaWMoKWAgYW5kIHN0b3JlZCBhcyBgcGFsYC4KYGBge3J9CnBhbCA8LSBjb2xvck51bWVyaWMoCiAgcGFsZXR0ZSA9ICJSZEJ1IiwgIyBSZWQgdG8gQmx1ZSBwYWxldHRlCiAgZG9tYWluID0gYmVldGxlJE1GUmF0aW8KKQoKCmxlYWZsZXQoYmVldGxlKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRDaXJjbGVzKGNvbG9yID0gfnBhbChNRlJhdGlvKSwgZmlsbE9wYWNpdHkgPSAuNywgcmFkaXVzID0gMTUwMDAsIHN0cm9rZSA9IEZBTFNFLAogICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZTAoYmVldGxlJE1GUmF0aW8pKQpgYGAKRGFya2VyIGJsdWUgY2lyY2xlcyByZXByZXNlbnQgc2l0ZXMgd2l0aCBhIGxhcmdlciBtYWxlIHRvIGZlbWFsZSByYXRpby4gRGFya2VyIHJlZCBjaXJjbGVzIHJlcHJlc2VudCBzaXRlcyB3aXRoIGEgc21hbGxlciBtYWxlIHRvIGZlbWFsZSByYXRpby4gV2Ugd2lsbCBzZWUgaG93IHRvIGFkZCBhIGxlZ2VuZCBsYXRlciBpbiB0aGUgbGVjdHVyZS4KCgojIyMgSGlnaGxpZ2h0CgpJbmRpdmlkdWFsIHNoYXBlcyBjYW4gYmUgaGlnaGxpZ2h0ZWQgd2hlbiBob3ZlcmVkIG92ZXIgdXNpbmcgYGhpZ2hsaWdodE9wdGlvbnNgIHdpdGhpbiB0aGUgc2hhcGUgbGF5ZXIuCmBgYHtyfQpsZWFmbGV0KGJlZXRsZSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkQ2lyY2xlcyhmaWxsQ29sb3IgPSBwYWwoYmVldGxlJE1GUmF0aW8pLCBmaWxsT3BhY2l0eSA9IC43LCByYWRpdXMgPSAxNTAwMCwgCiAgICAgICAgICAgICB3ZWlnaHQgPSAxLCBjb2xvciA9ICJibGFjayIsCiAgaGlnaGxpZ2h0T3B0aW9ucyA9IGhpZ2hsaWdodE9wdGlvbnMoY29sb3IgPSAieWVsbG93Iiwgd2VpZ2h0ID0gNCwKICAgICAgYnJpbmdUb0Zyb250ID0gVFJVRSkpCmBgYApIb3ZlciB5b3VyIG1vdXNlIG92ZXIgZGlmZmVyZW50IGNpcmNsZXMgYW5kIG5vdGljZSB0aGUgdGhlIHN0cm9rZSBjb2xvciBjaGFuZ2VzIGZyb20gYmxhY2sgdG8geWVsbG93IGFuZCBiZWNvbWVzIHRoaWNrZXIuIElmIGNpcmNsZXMgb3ZlcmxhcCwgaG92ZXJpbmcgbm93IGJyaW5ncyB0aGF0IGNpcmNsZSBpbiBmcm9udCBvZiB0aGUgb3RoZXJzLgoKIyMgTWFwIEdyb3VwcwoKQXNzaWduIGxheWVycyB0byBncm91cHMgdXNpbmcgYGdyb3VwID0gYC4gQXNzaWduaW5nIGdyb3VwcyBhbGxvd3MgeW91IHRvIHNob3cvaGlkZSBsYXllcnMgb3IgY29udHJvbCB0aGUgdmlzaWJpbGl0eSBvZiBsYXllcnMgdGhyb3VnaCB0aGUgZnVuY3Rpb24gYGFkZExheWVyc0NvbnRyb2woKWAKCkEgZ3JvdXAgY2FuIGJlIG1hZGUgdXAgYSBhIHNpbmdsZSBsYXllciBvciBtdWx0aXBsZSBsYXllcnMsIGJ1dCBlYWNoIGxheWVyIGNhbiBvbmx5IGJlbG9uZyB0byBvbmUgZ3JvdXAuCgojIyMgTGF5ZXIgQ29udHJvbHMKCk9uY2UgeW91ciBkYXRhIGxheWVycyBoYXZlIGJlZW4gYXNzaWduZWQgdG8gZ3JvdXBzIHlvdSBjYW4gdXNlIGBhZGRMYXllcnNDb250cm9sKClgIHRvIGNvbnRyb2wgaW5kaXZpZHVhbCBsYXllcnMKCgpJbiB0aGlzIGV4YW1wbGUgc2l0ZXMgd2l0aCBtb3JlIG1hbGVzIHRoYW4gZmVtYWxlcyBhcmUgYmx1ZSBhbmQgc2l0ZXMgd2l0aCBtb3JlIGZlbWFsZXMgdGhhbiBtYWxlcyBhcmUgcGluay4gQmVjYXVzZSB3ZSB3YW50IHRvIGNvbnRyb2wgdGhlIHR3byBkaWZmZXJlbnQgdHlwZXMgb2YgY2lyY2xlcyBzZXBhcmF0ZWx5LCB0aGV5IG5lZWQgdG8gYmUgYWRkZWQgYXMgaW5kaXZpZHVhbCBsYXllcnMuIFRoZSBgZ3JvdXAgPWAgaXMgZGVmaW5lZCBpbnNpZGUgZWFjaCBgYWRkQ2lyY2xlcygpYCBsYXllci4gCgpVbmRlciB0aGUgYGFkZExheWVyc0NvbnRyb2woKWAgbGF5ZXIgd2UgY2FuIHNlbGVjdCB3aGljaCBncm91cHMgdG8gY29udHJvbC4gSW4gdGhpcyBjYXNlIEkgd2FudCB0byBiZSBhYmxlIHRvIHR1cm4gdGhlIGxheWVycyBvbi9vZmYuIGBvdmVybGF5R3JvdXBzYCBjYW4gYmUgaW5kaXZpZHVhbGx5IGNoZWNrZWQgb3IgdW5jaGVja2VkLgpgYGB7cn0KCmxlYWZsZXQoKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRDaXJjbGVzKGRhdGEgPSAoYmVldGxlICU+JSBmaWx0ZXIoTWFsZXMgPiBGZW1hbGVzKSksIAogICAgICAgICAgICAgZmlsbENvbG9yID0gImxpZ2h0Ymx1ZSIsIGZpbGxPcGFjaXR5ID0gMSwgCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgcmFkaXVzID0gMTAwMDAsIHdlaWdodCA9IDEsCiAgICAgICAgICAgICBncm91cCA9ICJtYWxlcyIpICU+JQogIGFkZENpcmNsZXMoZGF0YSA9IChiZWV0bGUgJT4lIGZpbHRlcihNYWxlcyA8IEZlbWFsZXMpKSwgCiAgICAgICAgICAgICBmaWxsQ29sb3IgPSAicGluayIsIGZpbGxPcGFjaXR5ID0gMSwgCiAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCByYWRpdXMgPSAxMDAwMCwgd2VpZ2h0ID0gMSwKICAgICAgICAgICAgIGdyb3VwID0gImZlbWFsZXMiKSAlPiUKICBhZGRMYXllcnNDb250cm9sKG92ZXJsYXlHcm91cHMgPSBjKCJtYWxlcyIsICJmZW1hbGVzIiksCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gRkFMU0UpKQoKYGBgCiAgIAojIyMgQmFzZSBHcm91cHMKCmBiYXNlR3JvdXBzYCBjYW4gb25seSBiZSB2aWV3ZWQgb25lIGdyb3VwIGF0IGEgdGltZSBhbmQgb25lIGdyb3VwIGlzIGFsd2F5cyBzZWxlY3RlZCAoY2FuJ3QgdHVybiB0aGVtIGFsbCBvZmYpCgpCYXNlIGdyb3VwcyBhcmUgdXNlZnVsIHdoZW4gcHJvdmlkaW5nIHZpZXdlcnMgYSBjaG9pY2Ugb2YgYmFzZW1hcHMuIEluIHRoZSBleGFtcGxlIGJlbG93LCB0aHJlZSBkaWZmZXJlbnQgdGlsZSBsYXllcnMgYXJlIGFkZGVkLCBlYWNoIHRoZWlyIG93biBncm91cC4gTm93IGluIGBhZGRMYXllcnNDb250cm9sKClgIHdlIGNhbiBpbmNsdWRlIHRoZXNlIGJhc2VtYXAgZ3JvdXBzIGluIHRoZSBgYmFzZUdyb3Vwc2AuCmBgYHtyfQpsZWFmbGV0KCkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbiwgZ3JvdXAgPSAiRGVmYXVsdCIpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJE9wZW5TdHJlZXRNYXAsIGdyb3VwID0gIk9wZW4gU3RyZWV0IE1hcHMiKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXIsIGdyb3VwID0gIlN0YW1lbiBUb25lciIpICU+JQogIGFkZENpcmNsZXMoZGF0YSA9IChiZWV0bGUgJT4lIGZpbHRlcihNYWxlcyA+IEZlbWFsZXMpKSwgCiAgICAgICAgICAgICBmaWxsQ29sb3IgPSAibGlnaHRibHVlIiwgZmlsbE9wYWNpdHkgPSAxLCAKICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCByYWRpdXMgPSAxMDAwMCwgd2VpZ2h0ID0gMSwKICAgICAgICAgICAgIGdyb3VwID0gIm1hbGVzIikgJT4lCiAgYWRkQ2lyY2xlcyhkYXRhID0gKGJlZXRsZSAlPiUgZmlsdGVyKE1hbGVzIDwgRmVtYWxlcykpLCAKICAgICAgICAgICAgIGZpbGxDb2xvciA9ICJwaW5rIiwgZmlsbE9wYWNpdHkgPSAxLCAKICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsIHJhZGl1cyA9IDEwMDAwLCB3ZWlnaHQgPSAxLAogICAgICAgICAgICAgZ3JvdXAgPSAiZmVtYWxlcyIpICU+JQogIGFkZExheWVyc0NvbnRyb2wob3ZlcmxheUdyb3VwcyA9IGMoIm1hbGVzIiwgImZlbWFsZXMiKSwKICAgICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBsYXllcnNDb250cm9sT3B0aW9ucyhjb2xsYXBzZWQgPSBGQUxTRSksCiAgICAgICAgICAgICAgICAgICBiYXNlR3JvdXBzID0gYygiRGVmYXVsdCIsICJPcGVuIFN0cmVldCBNYXBzIiwgIlN0YW1lbiBUb25lciIpKQpgYGAKCk5vdGljZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIG92ZXJsYXkgZ3JvdXBzIChjaXJjbGVzKSBhbmQgYmFzZSBncm91cHMgKGJhc2VtYXBzKQoKCiMjIyBab29tIExldmVscwoKV2FudCB0byBkaXNwbGF5IG1vcmUgZGV0YWlsIHdoZW4gdGhlIG1hcCBpbiB6b29tZWQgaW4gYW5kIGxlc3MgZGV0YWlsIHdoZW4gdGhlIG1hcCBpcyB6b29tZWQgb3V0PyBUaGlzIGlzIGRvbmUgd2l0aCBgem9vbUxldmVsc2AgaW4gdGhlIGBncm91cE9wdGlvbnMoKWAKYGBge3J9CmJlZXRsZQoKbGVhZmxldChiZWV0bGUpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZENpcmNsZXMoZmlsbENvbG9yID0gImxpZ2h0Ymx1ZSIsIGZpbGxPcGFjaXR5ID0gMSwgCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgcmFkaXVzID0gMTAwMDAsIHdlaWdodCA9IDEsCiAgICAgICAgICAgICBncm91cCA9ICJiZWV0bGVzIikgJT4lCiAgZ3JvdXBPcHRpb25zKCJiZWV0bGVzIiwgem9vbUxldmVscyA9IDU6OCkKYGBgClpvb20gaW4gYW5kIG91dCB0byB3YXRjaCB0aGUgbGF5ZXIgdHVybiBvbi9vZmYKCgojIyMgQ2x1c3RlcmluZwoKCldoZW4gdGhlcmUgYXJlIGEgbGFyZ2VyIG51bWJlciBvZiBtYWtlcnMgb24gYSBtYXAgeW91IGNhbiBjbHVzdGVyIHRoZW0gdXNpbmcgYGNsdXN0ZXJPcGlvbnMgPWAKCkEgY29tbW9uIGV4YW1wbGUgb2YgY2x1c3RlcmluZyBpcyB0aGUgW0RvbWluaW9uIFBvd2VyIG91dGFnZSBtYXBdKGh0dHBzOi8vb3V0YWdlbWFwLmRvbWluaW9uZW5lcmd5LmNvbS9leHRlcm5hbC9kZWZhdWx0Lmh0bWwpLgoKYGBge3J9CmxlYWZsZXQoYmVldGxlKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGZpbGxDb2xvciA9ICJsaWdodGJsdWUiLCBmaWxsT3BhY2l0eSA9IDEsIAogICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIHdlaWdodCA9IDEsCiAgICAgICAgICAgICBncm91cCA9ICJiZWV0bGVzIiwKICAgICAgICAgICAgIGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMoKSkgCmBgYApIb3ZlcmluZyBvdmVyIGEgY2x1c3RlciBtYXJrZXIgd2l0aCB5b3VyIG1vdXNlIGFsbG93cyB5b3UgdG8gc2VlIHRoZSBjb3ZlcmFnZSBvZiB0aGUgY2x1c3RlcgoKQ2xpY2sgb24gYSBjbHVzdGVyIHRvIHpvb20gaW4gdG8gdGhlIGNsdXN0ZXIgYm91bmRzCgpBbGwgb2YgdGhlc2UgZmVhdHVyZXMgY2FuIGJlIGNoYW5nZWQgYG1hcmtlckNsdXN0ZXJPcHRpb25zYC4gYG1hcmtlckNsdXN0ZXJPcHRpb25zYCBhbHNvIGFsbG93cyB5b3UgdG8gZnJlZXplIHRoZSBjbHVzdGVyaW5nIGF0IGEgZGVmaW5lZCB6b29tIGxldmVsIHdpdGggYGZyZWV6ZUF0Wm9vbSA9IGAKCmBgYHtyfQpsZWFmbGV0KGJlZXRsZSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhmaWxsQ29sb3IgPSAibGlnaHRibHVlIiwgZmlsbE9wYWNpdHkgPSAxLCAKICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCB3ZWlnaHQgPSAxLAogICAgICAgICAgICAgZ3JvdXAgPSAiYmVldGxlcyIsCiAgICAgICAgICAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKAogICAgICAgICAgICAgICBzaG93Q292ZXJhZ2VPbkhvdmVyID0gRkFMU0UsCiAgICAgICAgICAgICAgIGZyZWV6ZUF0Wm9vbSA9IDYpKSAKYGBgCk5vdyB0aGUgY2x1c3RlcnMgc3RheSB0aGUgc2FtZSByZWdhcmRsZXNzIG9mIHpvb20gbGV2ZWwuIFdoZW4gdGhlIGNsdXN0ZXIgaXMgY2xpY2tlZCB5b3UgY2FuIHNlZSBlYWNoIHBvaW50IGluY2x1ZGVkIGluIHRoZSBjbHVzdGVyCgojIyBMZWdlbmRzCgpMZWdlbmRzIGFyZSBhZGRlZCB1c2luZyBgYWRkTGVnZW5kKClgCgotIFlvdSBtdXN0IHByb3ZpZGUgY29sb3IgaW5mb3JtYXRpb24KICAKICAtIGBwYWwgPWAgaWYgdXNpbmcgYSBwYWxldHRlLCBvciBgY29sb3IgPSBgIGlmIHVzaW5nIGN1c3RvbSBkZWZpbmVkIGNvbG9ycwoKLSBZb3UgbXVzdCBwcm92aWRlIHRoZSB2YWx1ZXMgdXNlZCB0byBnZW5lcmF0ZSBjb2xvcnMgZnJvbSB0aGUgcGFsZXR0ZSBvciB0aGUgbGFiZWxzIGZvciBlYWNoIGNvcnJlc3BvbmRpbmcgY29sb3IKCgpUaGUgZXhhbXBsZSBiZWxvdyBzaG93cyBhIGxlZ2VuZCB3aXRoIGNhdGVnb3JpY2FsIGRhdGEKYGBge3J9CmxlYWZsZXQoKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRDaXJjbGVzKGRhdGEgPSAoYmVldGxlICU+JSBmaWx0ZXIoTWFsZXMgPiBGZW1hbGVzKSksIAogICAgICAgICAgICAgZmlsbENvbG9yID0gImxpZ2h0Ymx1ZSIsIGZpbGxPcGFjaXR5ID0gMSwgCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgcmFkaXVzID0gMTAwMDAsIHdlaWdodCA9IDEsCiAgICAgICAgICAgICBncm91cCA9ICJtYWxlcyIpICU+JQogIGFkZENpcmNsZXMoZGF0YSA9IChiZWV0bGUgJT4lIGZpbHRlcihNYWxlcyA8IEZlbWFsZXMpKSwgCiAgICAgICAgICAgICBmaWxsQ29sb3IgPSAicGluayIsIGZpbGxPcGFjaXR5ID0gMSwgCiAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCByYWRpdXMgPSAxMDAwMCwgd2VpZ2h0ID0gMSwKICAgICAgICAgICAgIGdyb3VwID0gImZlbWFsZXMiKSAlPiUKICBhZGRMZWdlbmQoIGNvbG9ycyA9IGMoImxpZ2h0Ymx1ZSIsICJwaW5rIiksCiAgICAgICAgICAgICBsYWJlbHMgPSBjKCJNb3JlIE1hbGVzIiwgIk1vcmUgRmVtYWxlcyIpLAogICAgICAgICAgICAgb3BhY2l0eSA9IDEpCmBgYAogICAKVGhlIG5leHQgZXhhbXBsZSBzaG93cyBhIGxlZ2VuZCB3aXRoIGNvbnRpbnVvdXMgZGF0YS4gTm90aWNlIHRoYXQgYHBhbGAgaXMgdXNlZCB0aGlzIHRpbWUgaW5zdGVhZCBvZiBgY29sb3JgIGFuZCBgdmFsdWVzYCBpbnN0ZWFkIG9mIGBsYWJlbHNgCmBgYHtyfQpwYWwgPC0gY29sb3JOdW1lcmljKAogIHBhbGV0dGUgPSAiUmRCdSIsCiAgZG9tYWluID0gYmVldGxlJE1GUmF0aW8pCgpsZWFmbGV0KGJlZXRsZSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkQ2lyY2xlcyhmaWxsQ29sb3IgPSB+cGFsKE1GUmF0aW8pLCBmaWxsT3BhY2l0eSA9IC43LCByYWRpdXMgPSAxNTAwMCwgCiAgICAgICAgICAgICB3ZWlnaHQgPSAxLCBjb2xvciA9ICJncmV5IikgJT4lCiAgYWRkTGVnZW5kKHBhbCA9IHBhbCwgdmFsdWVzID0gfk1GUmF0aW8sCiAgICAgICAgICAgIHRpdGxlID0gIk1hbGUgdG8gRmVtYWxlIFJhdGlvIikKYGBgCgoKCiMjIFNjYWxlIEJhcnMKClNjYWxlIGJhcnMgYXJlIGFkZGVkIHVzaW5nIGBhZGRTY2FsZUJhcigpYC4gVGhlIHNjYWxlIGJhciB3aWxsIGFkanVzdCBpdHNlbGYgYXMgeW91IHpvb20gaW4gYW5kIG91dApgYGB7cn0KbGVhZmxldChiZWV0bGUpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZmlsbENvbG9yID0gImxpZ2h0Ymx1ZSIsIGZpbGxPcGFjaXR5ID0gMSwgCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgd2VpZ2h0ID0gMSkgJT4lCiAgYWRkU2NhbGVCYXIocG9zaXRpb24gPSAiYm90dG9tcmlnaHQiKQpgYGAKCgpZb3UgY2FuIGNob29zZSB0byB1c2UgbWV0cmljIGFuZC9vciBpbXBlcmlhbCB1bml0cyBhbmQgYWRqdXN0IHRoZSBzY2FsZSBiYXIgd2lkdGggdXNpbmcgYG9wdGlvbnMgPSBzY2FsZUJhck9wdGlvbnNgLiAKCgojIyBMZWFmbGV0IEV4dHJhcyBQYWNrYWdlCgpUaGUgYGxlYWZsZXQuZXh0cmFzYCBwYWNrYWdlIGFsbG93cyBmb3IgYWRkaXRpb25hbCBmdW5jdGlvbmFsaXR5IHZpYSBsZWFmbGV0IHBsdWdpbnMuIFdlJ2xsIHRha2UgYSBsb29rIGF0IHRocmVlIGV4YW1wbGVzIGhlcmUsIGJ1dCB0aGVyZSBhcmUgW3RvbnMgb2YgcGx1Z2luc10oaHR0cHM6Ly9iaGFza2FydmsuZ2l0aHViLmlvL2xlYWZsZXQuZXh0cmFzLykgdG8gY2hvb3NlIGZyb20gCgojIyMgUmVzZXQKCmBhZGRSZXNldE1hcEJ1dHRvbigpYCByZXNldHMgeW91IG1hcCB0byB0aGUgb3JpZ2luYWwgdmlldyBhbmQgem9vbSBsZXZlbApgYGB7cn0KbGlicmFyeShsZWFmbGV0LmV4dHJhcykKCmxlYWZsZXQoYmVldGxlKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGZpbGxDb2xvciA9ICJsaWdodGJsdWUiLCBmaWxsT3BhY2l0eSA9IDEsIAogICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIHdlaWdodCA9IDEpICU+JQogIGFkZFJlc2V0TWFwQnV0dG9uKCkKYGBgCk1vdmUgdGhlIG1hcCBhbmQgem9vbSBpbi9vdXQsIHRoZW4gcHJlc3MgdGhlIHJlc2V0IGJ1dHRvbiB0byBtYWtlIHRoZSBtYXAgcmV0dXJuIHRvIGl0cyBzdGFydGluZyB2aWV3IGFuZCB6b29tIGxldmVsCgoKIyMjIFNlYXJjaCBCYXJzCgpgYWRkU2VhcmNoT1NNKClgIGFuZCBgYWRkU2VhcmNoR29vZ2xlKClgIGFsbG93IHlvdSB0byBzZWFyY2ggZm9yIGxvY2F0aW9ucyBvbiB0aGUgbWFwCmBgYHtyfQpsZWFmbGV0KGJlZXRsZSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhmaWxsQ29sb3IgPSAibGlnaHRibHVlIiwgZmlsbE9wYWNpdHkgPSAxLCAKICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCB3ZWlnaHQgPSAxKSAlPiUKICBhZGRTZWFyY2hPU00ob3B0aW9ucyA9IHNlYXJjaE9wdGlvbnMoCiAgICB6b29tID0gOCwKICAgIGhpZGVNYXJrZXJPbkNvbGxhcHNlID0gVFJVRSwKICAgIGF1dG9Db2xsYXBzZSA9IEZBTFNFKSkKYGBgCiAgIAoKVHJ5IHNlYXJjaGluZyBmb3IgIkNhYm8gU2FuIEx1Y2FzIgoKIyMjIERyYXcgVG9vbGJhcgoKYGFkZERyYXdUb29sYmFyKClgIGFsbG93cyB5b3UgdG8gZHJhdyBwb2ludHMsIGxpbmVzIGFuZCBwb2x5Z29ucyBvbiBtYXAKYGBge3J9CmxlYWZsZXQoYmVldGxlKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGZpbGxDb2xvciA9ICJsaWdodGJsdWUiLCBmaWxsT3BhY2l0eSA9IDEsIAogICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIHdlaWdodCA9IDEpICU+JQogIGFkZERyYXdUb29sYmFyKAogICAgZWRpdE9wdGlvbnMgPSBlZGl0VG9vbGJhck9wdGlvbnMoCiAgICAgIHNlbGVjdGVkUGF0aE9wdGlvbnMgPSBzZWxlY3RlZFBhdGhPcHRpb25zKCkgKSkgJT4lCiAgcmVtb3ZlRHJhd1Rvb2xiYXIoKQpgYGAKVHJ5IGRyYXdpbmcgYW5kIHRoZW4gZGVsZXRpbmcgYSBzaGFwZSBmcm9tIHlvdXIgbWFwCgpgYWRkU3RseWVFZGl0b3IoKWAgYWxsb3dzIHlvdSB0byBlZGl0IHRoZSBhcHBlYXJhbmNlIG9mIHNoYXBlcwoKIyMjIFB1bHNpbmcgSWNvbgoKQ3JlYXRlIHB1bHNpbmcgaWNvbnMgdXNpbmcgYHB1bHNlSWNvbnMoKWAgYW5kIGFkZCB0aGVtIHRvIHlvdXIgbWFwIHVzaW5nIGBhZGRQdWxzZU1hcmtlcnMoKWAKYGBge3J9Cmljb24gPC0gcHVsc2VJY29ucyhjb2xvciA9ICIjZmYwMDAwIiwgaWNvblNpemUgPSAxMiwgYW5pbWF0ZSA9IFRSVUUsCiAgaGVhcnRiZWF0ID0gMSkKCmxlYWZsZXQoKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBzZXRWaWV3KGxuZyA9IC0xMTEsIGxhdCA9IDI1LCB6b29tID0gNykgJT4lCiAgYWRkUHVsc2VNYXJrZXJzKGxuZyA9IC0xMTEsIGxhdCA9IDI1LCBpY29uID0gaWNvbikKYGBgCgoKIyMgTWFraW5nIGEgQ2hvcm9wbGV0aCBNYXAKCkxpbmVzIGFuZCBQb2x5Z29ucyB3b3JrIGluIGEgdmVyeSBzaW1pbGFyIG1hbm5lciB0byBjaXJjbGVzCgpGb3IgdGhpcyBleGFtcGxlIHdlJ2xsIHVzZSBjZW5zdXMgdHJhY3RzIGZyb20gdGhlIGB0aWdyaXNgIHBhY2thZ2UKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlncmlzKQpvcHRpb25zKHRpZ3Jpc191c2VfY2FjaGUgPSBUUlVFKQoKdHJhY3RzIDwtIHRyYWN0cygiVkEiLCAiUmljaG1vbmQgY2l0eSIpICU+JQogIHN0X3RyYW5zZm9ybSg0MzI2KQpgYGAKCkRlZmluaW5nIHRoZSBjb2xvciBwYWxldHRlIHVzaW5nIHRoZSBsYW5kIGFyZWEgb2YgZWFjaCB0cmFjdC4gQXJlYSBpcyBnaXZlbiBpbiBzcXVhcmUgbWV0ZXJzLiBUbyBjb252ZXJ0IHRvIHNxdWFyZSBtaWxlcyB3ZSBkaXZpZGUgb3VyIGFyZWEgYnkgMi41OWUrNgpgYGB7cn0KcGFsIDwtIGNvbG9yTnVtZXJpYyhwYWxldHRlID0gIkJsdWVzIiwKICAgICAgICAgICAgICAgICAgICBkb21haW4gPSB0cmFjdHMkQUxBTkQvMi41OWUrNikgCmBgYAoKVGhlIHBvbHlnb24gY29sb3JzIGFyZSBhc3NpZ25lZCB1c2luZyBgZmlsbENvbG9yID0gfnBhbChBTEFORC8yLjU5ZSs2KWAKCkxhYmVscywgbGVnZW5kIGFuZCBzY2FsZSBiYXIgYXJlIGFkZGVkIHRvIGdpdmUgdGhlIG1hcCBhIG1vcmUgcG9saXNoZWQgZmluaXNoCmBgYHtyIGVjaG89VFJVRX0KbGVhZmxldCh0cmFjdHMpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZFBvbHlnb25zKGZpbGxDb2xvciA9IH5wYWwoQUxBTkQvMi41OWUrNiksICBmaWxsT3BhY2l0eSA9IDEsIHNtb290aEZhY3RvciA9IC4yLAogICAgICAgICAgICAgIGNvbG9yID0gImRhcmtncmV5Iiwgd2VpZ2h0ID0gMSwKICAgICAgICAgICAgICAgIGhpZ2hsaWdodE9wdGlvbnMgPSBoaWdobGlnaHRPcHRpb25zKGNvbG9yID0gIndoaXRlIiwgd2VpZ2h0ID0gMiwgb3BhY2l0eSA9IDEsCiAgICAgICAgICAgICAgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwKICAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlKHJvdW5kKHRyYWN0cyRBTEFORC8yLjU5ZSs2LCBkaWdpdHMgPSAyKSwgInNxLiBtaS4iKQogICAgICAgICAgICAgICkgJT4lCiAgYWRkTGVnZW5kKHBhbCA9IHBhbCwgdmFsdWVzID0gfkFMQU5ELzIuNTllKzYsIHRpdGxlID0gIkFyZWEgKHNxLiBtaWxlcykiKSAlPiUKICBhZGRTY2FsZUJhcihwb3NpdGlvbiA9ICJib3R0b21yaWdodCIpCmBgYApUaGUgbWFwIGlzIG5vdyBzaGFkZWQgc28gdGhhdCB0aGUgbGFyZ2VyIGNlbnN1cyB0cmFjdHMgYXJlIGRhcmtlciBibHVlIGFuZCB0aGUgc21hbGxlc3QgdHJhY3RzIGFyZSBsaWdodGVyIGJsdWUKCgoKCgoKCgoKCgoK